iT邦幫忙

2024 iThome 鐵人賽

DAY 20
1
Software Development

深入淺出Java 30天系列 第 20

Day 20: 最好使用composition而不是繼承(上)

  • 分享至 

  • xImage
  •  

使用繼承雖然可以重複使用程式碼,但是繼承會有一些缺點,像是:

  • 父類別未來如果有變動,容易影響子類別的行為,範圍如果過大,會無法預測系統的行為。
  • 繼承的時候,如果不夠了解父類別的行為,子類別覆寫方法時,容易有不預期的結果。

上面的兩個缺點,都顯示了一個問題,繼承容易有不預期的結果。以下面的範例來說,InstrumentedHashSet繼承了HashSet,並用addCount紀錄有多少item被加進去set,但因為HashSet在執行addAll的時候會呼叫add,所以addCount被重複計算,也許可以重寫addAlladd邏輯,甚至在加一個新的方法改善這個問題,但卻都不是好的解決方式。

import java.util.Arrays;
import java.util.Collection; 
import java.util.HashSet;

// Broken - Inappropriate use of inheritance!
public class InstrumentedHashSet<E> extends HashSet<E> {
    // The number of attempted element insertions
    private int addCount = 0;
    public InstrumentedHashSet() {
    }
    public InstrumentedHashSet(int initCap, float loadFactor) {
      super(initCap, loadFactor);
    }
    @Override public boolean add(E e) {
      addCount++;
      return super.add(e);
    }
    @Override public boolean addAll(Collection<? extends E> c) {
      addCount += c.size();
      return super.addAll(c);
    }
    public int getAddCount() {
      return addCount;
    }

    public static void main(String[] args) {
        InstrumentedHashSet<String> s = new InstrumentedHashSet<>();
        s.add("a");
        s.add("b");
        s.addAll(Arrays.asList("c", "d", "e"));

        System.out.println(s.getAddCount()); // 應該輸出 5, 但是結果是 8
        System.out.println(s); // 應該輸出 [a, b, c, d, e]
    }
}

因為改寫和加方法也有可能遇到一些問題:

  • 重寫工程浩大,耗費時間,也失去原本繼承可以重複使用程式碼的意義。
  • 原本的父類別如果有使用一些private的方法,為了維持功能完整,勢必需要自行重寫這些方法。
  • 在子類別加方法,如果未來父類別也加同樣名稱的方法,子類別原先加的方法會造成compile失敗。

由於上述的缺點,繼承比較適合在package裡面小規模使用,如果類別的使用範圍很廣,也許使用composition去實作需要擴充的功能,建立一個新的類別,並把原有類別的一個欄位指到新的類別,新類別就可以作為該模組的一個新功能,是一個比較好的選擇。

上面的範例原本是繼承HashSet來擴充功能,但可以改成實作Set這個interface,並宣告set這個欄位指向HashSet的物件,接著實作Set的方法時,如果需要改變方法的行為,完全可以依照自己的邏輯實作,但如果不需要改變方法的行為,也可以直接回傳set的執行結果,重複使用HashSet的程式碼。

import java.util.*;

public class InstrumentedHashSet<E> implements Set<E> {
    // The number of attempted element insertions
    private int addCount = 0;
    private final Set<E> set;

    public InstrumentedHashSet() {
        set = new HashSet<>();
    }

    public boolean add(E e) {
        addCount++;
        return set.add(e);
    }

    public boolean addAll(Collection<? extends E> c) {
        addCount += c.size();
        return set.addAll(c);
    }

    public int getAddCount() {
        return addCount;
    }

    @Override
    public String toString() {
        return set.toString();
    }

    public int size() {
        return set.size();
    }

    public boolean isEmpty() {
        return set.isEmpty();
    }

    public boolean contains(Object o) {
        return set.contains(o);
    }

    public Iterator<E> iterator() {
        return set.iterator();
    }

    public Object[] toArray() {
        return set.toArray();
    }

    public <T> T[] toArray(T[] a) {
        return set.toArray(a);
    }

    public boolean remove(Object o) {
        return set.remove(o);
    }

    public boolean containsAll(Collection<?> c) {
        return set.containsAll(c);
    }

    public boolean retainAll(Collection<?> c) {
        return set.retainAll(c);
    }

    public boolean removeAll(Collection<?> c) {
        return set.removeAll(c);
    }

    public void clear() {
        set.clear();
    }
    
    public static void main(String[] args) {
        InstrumentedHashSet<String> s = new InstrumentedHashSet<>();
        s.add("a");
        s.add("b");
        s.addAll(Arrays.asList("c", "d", "e"));

        System.out.println(s.getAddCount()); // 應該輸出 5
        System.out.println(s); // 應該輸出 [a, b, c, d, e]
    }
}

今天就介紹到這裡,明天會介紹使用composition改寫繼承更彈性的作法~


上一篇
Day 19: 最小化可變性(下)
下一篇
Day 21: 最好使用composition而不是繼承(下)
系列文
深入淺出Java 30天30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

1
阿拉伯沙忙
iT邦新手 5 級 ‧ 2024-08-21 00:07:29

超棒誒~快完成了

對,崩潰java讀書心得30天只剩最後10天了/images/emoticon/emoticon07.gif

我要留言

立即登入留言